// -----------------------------------------------------------------------------
// Copyright (c) 2014-2024 All rights reserved
// -----------------------------------------------------------------------------
// Author		: HiDark 1173296519@qq.com
// File			: sdram.v
// Create		: 2024-6-13
// Description	: SDRAM simulation model MT48LC16M16A2
// Editor		: tab size (4)
// -----------------------------------------------------------------------------

`define DEBUG

module sdram(
  input        clk,
  input        cke, //  clk enable ,high -> auto refresh
  input        cs,
  input        ras,
  input        cas,
  input        we,
  input [12:0] a,
  input [ 1:0] ba,
  input [ 1:0] dqm, // 15:8 / 7:0
  inout [15:0] dq
);
//the command of the sdram
localparam CMD_INHIBT       = 4'b1xxx;
localparam NOP              = 4'b0111;
localparam ACTIVATE         = 4'b0011;
localparam READ             = 4'b0101;
localparam WRITE            = 4'b0100;
localparam BURST_STOP       = 4'b0110;
localparam PRECHARGE        = 4'b0010;
localparam AUTO_REFRESH     = 4'b0001;
localparam LOAD_MODE_REG    = 4'b0000;

localparam ST_IDLE          = 2'b00;
localparam ST_WRITE         = 2'b01;
localparam ST_READ          = 2'b10;
// 4banks,each bank has 8192 rows and 512 columns 
reg  [15:0] Bank0 [0 : 8192*512-1];
reg  [15:0] Bank1 [0 : 8192*512-1];
reg  [15:0] Bank2 [0 : 8192*512-1];
reg  [15:0] Bank3 [0 : 8192*512-1];
reg  [12:0] Row;
reg  [ 8:0] Col;
reg  [ 1:0] Bank;
reg  [12:0] mode_register; 
wire [ 2:0] cas_latcency = mode_register[6:4];
wire [ 3:0] sdram_cmd    = {cs,ras,cas,we};
reg  [ 3:0] burst_len;
reg  [ 3:0] burst_cnt;
reg  [ 2:0] cas_cnt;
reg  [15:0] din,dout;
reg  [ 1:0] dqm3,dqm2,dqm1;
reg  [ 1:0] nextstate,state;


always @(*) begin 
    case(mode_register[2:0])
        3'b000: burst_len = 'd1;
        3'b001: burst_len = 'd2;
        3'b010: burst_len = 'd4;  
        3'b011: burst_len = 'd8;
        default:burst_len = 'd0;// not support
    endcase
end

// LOAD MORE REGISTER
always @(posedge clk) begin
    if(sdram_cmd == LOAD_MODE_REG) begin
        mode_register <= a;
`ifdef DEBUG
        $display ("at time %t LMR  : Load Mode Register", $time);
        $display ("CAS Latency      = %d",a[6:4]);
        if(a[2:0] == 3'b000)
            $display ("Burst Length     = 1");
        else if(a[2:0] == 3'b001)
            $display ("Burst Length     = 2");
        else if(a[2:0] == 3'b010)
            $display ("Burst Length     = 4");
        else if(a[2:0] == 3'b011)
            $display ("Burst Length     = 8");
        else
            $display ("Burst Length     = CANNOT SUPPORT");            
`endif
    end
end

// FSM 
always @(posedge clk) begin 
    state   <= nextstate;
end

always @(posedge clk) begin 
    if(sdram_cmd == ACTIVATE) begin
        Row         <= a;
        Bank        <= ba;
    end
end

always @(*) begin 
    case(state)
        ST_IDLE:begin
            if(sdram_cmd == WRITE) nextstate = ST_WRITE;
            else if(sdram_cmd == READ) nextstate = ST_READ;
            else nextstate = state;
        end
        ST_WRITE:begin
            if(sdram_cmd == BURST_STOP || burst_cnt == burst_len)
                nextstate = ST_IDLE;
            else nextstate = state;
        end
        ST_READ:begin
            if(sdram_cmd == BURST_STOP || burst_cnt == burst_len)
                nextstate = ST_IDLE;
            else nextstate = state;
        end        
        default:nextstate = ST_IDLE;
    endcase
end
// pipeline dqm when read
always @(posedge clk) begin 
    if(nextstate == ST_READ )begin
        dqm3    <= dqm2;        
        dqm2    <= dqm1;
        dqm1    <= dqm;             
    end 
end

always @(posedge clk) begin 
    case(nextstate)
        ST_WRITE:begin
            // Burst Count
            if (burst_cnt < burst_len)
                burst_cnt = burst_cnt + 1; 
            else 
                burst_cnt = 0;                           
            // Array buffer
            if (state == ST_IDLE) 
                Col = a[8:0];
            // if (Bank != ba) begin
            //     $display("at time %t WRITE: Write Bank = %b but activate Bank = %b", $time, ba,Bank);
            //     $fatal;
            // end

            if (Bank == 2'b00)
                din = Bank0[{Row,Col}];
            else if (Bank == 2'b01)
                din = Bank1[{Row,Col}];  
            else if (Bank == 2'b10)
                din = Bank2[{Row,Col}];    
            else if (Bank == 2'b11)
                din = Bank3[{Row,Col}];   
            // Dqm operation
            if (dqm[0] == 1'b0) din[ 7 : 0] = dq[ 7 : 0];
            if (dqm[1] == 1'b0) din[15 : 8] = dq[15 : 8];

            if (Bank == 2'b00)
                Bank0[{Row,Col}] = din;
            else if (Bank == 2'b01)
                Bank1[{Row,Col}] = din;  
            else if (Bank == 2'b10)
                Bank2[{Row,Col}] = din;    
            else if (Bank == 2'b11)
                Bank3[{Row,Col}] = din;           
`ifdef DEBUG
    $display("at time %t WRITE: Bank = %d Row = %d, Col = %d, Data = %x, Dqm = %b", $time, Bank, Row, Col, din, dqm);
`endif
            Col = Col+1;
        end
        ST_READ:begin
            if(cas_cnt < cas_latcency)
                cas_cnt   = cas_cnt + 1;  
            else if (cas_cnt == cas_latcency && burst_cnt >= burst_len) 
                cas_cnt   = 0;                           
            if(cas_cnt == cas_latcency && burst_cnt < burst_len)
                burst_cnt = burst_cnt + 1; 
            else 
                burst_cnt = 0;     
            // Array buffer
            if(state == ST_IDLE) 
                Col = a[8:0];                        
            // Dqm operation
            if(cas_cnt == cas_latcency)begin
                if(cas_latcency == 3'd2)
                    begin
                        if (Bank == 2'b00)begin
                            dout[ 7:0] = dqm2[0]?8'dx:Bank0[{Row, Col}][ 7:0];
                            dout[15:8] = dqm2[1]?8'dx:Bank0[{Row, Col}][15:8];                              
                        end
                        else if (Bank == 2'b01)begin
                            dout[ 7:0] = dqm2[0]?8'dx:Bank1[{Row, Col}][ 7:0];
                            dout[15:8] = dqm2[1]?8'dx:Bank1[{Row, Col}][15:8];                             
                        end
                        else if (Bank == 2'b10)begin
                            dout[ 7:0] = dqm2[0]?8'dx:Bank2[{Row, Col}][ 7:0];
                            dout[15:8] = dqm2[1]?8'dx:Bank2[{Row, Col}][15:8];                             
                        end  
                        else if (Bank == 2'b11)begin
                            dout[ 7:0] = dqm2[0]?8'dx:Bank3[{Row, Col}][ 7:0];
                            dout[15:8] = dqm2[1]?8'dx:Bank3[{Row, Col}][15:8];                             
                        end                     
                    end
                else if(cas_latcency == 3'd3)
                    begin
                        if (Bank == 2'b00)begin
                            dout[ 7:0] = dqm3[0]?8'dx:Bank0[{Row, Col}][ 7:0];
                            dout[15:8] = dqm3[1]?8'dx:Bank0[{Row, Col}][15:8];                              
                        end
                        else if (Bank == 2'b01)begin
                            dout[ 7:0] = dqm3[0]?8'dx:Bank1[{Row, Col}][ 7:0];
                            dout[15:8] = dqm3[1]?8'dx:Bank1[{Row, Col}][15:8];                             
                        end
                        else if (Bank == 2'b10)begin
                            dout[ 7:0] = dqm3[0]?8'dx:Bank2[{Row, Col}][ 7:0];
                            dout[15:8] = dqm3[1]?8'dx:Bank2[{Row, Col}][15:8];                             
                        end  
                        else if (Bank == 2'b11)begin
                            dout[ 7:0] = dqm3[0]?8'dx:Bank3[{Row, Col}][ 7:0];
                            dout[15:8] = dqm3[1]?8'dx:Bank3[{Row, Col}][15:8];                             
                        end     
                    end
`ifdef DEBUG
                if(cas_latcency == 3'd2)
                    $display("at time %t  READ: Bank = %d Row = %d, Col = %d, Data = %x, Dqm = %b", $time, Bank, Row, Col, dout, dqm2);
                else if(cas_latcency == 3'd3)
                    $display("at time %t  READ: Bank = %d Row = %d, Col = %d, Data = %x, Dqm = %b", $time, Bank, Row, Col, dout, dqm3);
`endif
                Col = Col+1;
            end
        end
    default:begin
        burst_cnt = 0;
        cas_cnt   = 0;
        dout      = 0;
        Col       = 0;

    end
    endcase
end
wire        read_valid = (state == ST_READ);
assign dq = read_valid ? dout:16'dz;
endmodule
